package app

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	"io/ioutil"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"math/rand"
	"net"
	"net/http"
	"operator/pkg/format"
	tcp2 "operator/pkg/network/meshnet/tcp"
	"operator/pkg/service"
	"operator/pkg/tracing"
	"operator/pkg/types/resource"
	"operator/pkg/unixsock"
	"operator/pkg/util"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

const defaultPath = "D:\\gopath\\src\\operator\\config/kubeconfig"

type IngressServer struct {
	tcpClient *tcp2.NewTcpClient
	resource  *map[resource.ResourceString][]resource.Resource
	trace     tracing.Interface
	listener  net.Listener
	service   service.Interface
	cniUtil   *CniUtil
	cniSocket *unixsock.UnixSocket
	informer  informers.SharedInformerFactory
}

func (mc *IngressServer) updateResource(data []byte) []byte {
	err := json.Unmarshal(data, mc.resource)
	if err != nil {
		format.Errors("cmd/ingress/app/client.go", 39, err.Error())
		return nil
	}

	return []byte("ok")
}

func (mc *IngressServer) Run() {
	stopCh := make(chan struct{})
	err := mc.cniUtil.Add()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	err = mc.cniUtil.CopyBin()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	defer func() {
		err = mc.cniUtil.Recover()
		if err != nil {
			fmt.Println(err)
			return
		}
	}()

	mc.informer.Start(stopCh)
	go mc.tcpClient.Run(stopCh)
	mc.RunFirst()
	go mc.runTcp()
	go mc.cniSocket.Run(stopCh)
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGKILL, syscall.SIGINT)

	select {
	case x := <-ch:
		switch x {
		case syscall.SIGINT:
			format.Println("cmd/ingress/app/client.go", 58, "app stop")
		}
	}
}

func (mc *IngressServer) RunFirst() {
	for {
		if mc.tcpClient.Connected() {
			mc.getTraceIp()
			data, err := mc.tcpClient.List()
			if err != nil {
				format.Errors("cmd/ingress/app/client.go", 71, err.Error())
				return
			}
			mc.updateResource(data)

			return
		}

		time.Sleep(time.Second)
	}
}

func (mc *IngressServer) getTraceIp() {
	res, err := mc.tcpClient.Write("getTraceIp", []byte{})
	if err != nil {
		format.Errors("cmd/ingress/app/client.go", 86, err.Error())
		return
	}

	ip := string(res)
	if ip == "" {
		return
	}

	jaeger := tracing.NewJaegerByIp(ip, "ingress")
	if jaeger == nil {
		format.Errors("cmd/ingress/app/client.go", 97, "初始化jaeger失败")
		return
	}

	mc.trace = jaeger
}

func (mc *IngressServer) runTcp() {
	for {
		conn, err := mc.listener.Accept()
		if err != nil {
			format.Errors("cmd/ingress/app/client.go", 108, err.Error())
			continue
		}
		go mc.dealTcp(conn)
	}
}

func (ts *IngressServer) dealTcp(conn net.Conn) {
	buffer := make([]byte, 1024*30)
	for {
		n, err := conn.Read(buffer)
		if err != nil {
			format.Errors("cmd/ingress/app/client.go", 120, err.Error())
			break
		}
		var span opentracing.Span
		b := bytes.NewBuffer(buffer[:n])
		req, err := http.ReadRequest(bufio.NewReader(b))
		sendBuf := make([]byte, 1024*30)
		sendBuf = buffer[:n]
		if err != nil {
			format.Errors("cmd/ingress/app/client.go", 129, err.Error())
			util.HttpError(conn, err)
			continue
		}

		sc, err := ts.trace.Tracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
		if err != nil {
			span = ts.trace.StartSpan(req.URL.Path, nil)
		} else {
			span = ts.trace.StartSpan(req.URL.Path, sc)
		}
		ext.HTTPUrl.Set(span, req.URL.Path)
		ext.HTTPMethod.Set(span, req.Method)

		param, err := ioutil.ReadAll(req.Body)
		if err != nil {
			format.Errors("cmd/ingress/app/client.go", 144, err.Error())
			ext.HTTPStatusCode.Set(span, 500)
			span.SetTag("error", err.Error())
			span.Finish()
			util.HttpError(conn, err)
			break
		}

		span.SetTag("http_body", string(param))
		err = ts.trace.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
		if err != nil {
			format.Errors("cmd/ingress/app/client.go", 148, err.Error())
			ext.HTTPStatusCode.Set(span, 500)
			span.SetTag("error", err.Error())
			span.Finish()
			util.HttpError(conn, err)
			break
		}

		version := req.Header.Get("mesh_version")
		sendBuf = util.RequestParser(req)

		cbuf := make([]byte, 20*1024, 20*1024)
		cbuf, err = ts.writeToRemote(sendBuf, version)
		if err != nil {
			format.Errors("cmd/ingress/app/client.go", 162, err.Error())
			ext.HTTPStatusCode.Set(span, 500)
			span.SetTag("error", err.Error())
			span.Finish()

			util.HttpNotFound(conn)
			break
		}

		_, err = conn.Write(cbuf)
		if err != nil {
			format.Errors("cmd/ingress/app/client.go", 173, err.Error())
			ext.HTTPStatusCode.Set(span, 500)
			span.SetTag("error", err.Error())
			span.Finish()
			util.HttpError(conn, err)
			break
		}

		if span != nil {
			ext.HTTPStatusCode.Set(span, 200)
			span.SetTag("http_resp", string(cbuf))
			span.Finish()
		}
	}
}

func (ts *IngressServer) getPathFromBuf(buf []byte) string {
	str := string(buf)
	arr := strings.SplitN(str, "\r\n", 2)
	if len(arr) < 2 {
		return ""
	}

	arr = strings.Split(arr[0], " ")
	if len(arr) != 3 {
		return ""
	}

	path := arr[1]
	if strings.Contains(path, "?") {
		arr = strings.Split(path, "?")
		path = arr[0]
		if path[len(path)-1] == '/' {
			return path[:len(path)-1]
		}

		return path
	}

	return path
}

func (ts *IngressServer) getConnFromResource(r *resource.Resource) (net.Conn, error) {
	c, err := net.Dial("tcp", r.Host)
	if err != nil {
		format.Errors("cmd/ingress/app/client.go", 218, err.Error())
		return nil, err
	}

	return c, err
}

func (ts *IngressServer) getResource(path, version string) (r *resource.Resource) {
	if version == "" {
		version = "v1"
	}
	rstr := resource.GenerateResourceString(version, path)
	if lists, ok := (*ts.resource)[rstr]; ok {
		i := rand.Intn(len(lists))
		one := lists[i]
		return &one
	}

	return nil
}

func (ts *IngressServer) writeToRemote(buf []byte, version string) ([]byte, error) {
	path := ts.getPathFromBuf(buf)
	if path == "" {
		format.Errors("cmd/ingress/app/client.go", 242, "error path")
		return nil, errors.New("error path")
	}

	r := ts.getResource(path, version)
	if r == nil {
		format.Errors("cmd/ingress/app/client.go", 248, "error resource")
		return nil, errors.New("error resource")
	}

	remote, err := ts.getConnFromResource(r)
	if err != nil {
		return nil, err
	}

	var result []byte
	err = ts.service.Do(func() error {
		_, err = remote.Write(buf)
		if err != nil {
			return err
		}

		cbuf := make([]byte, 1024*30)
		n, err := remote.Read(cbuf)
		if err != nil {
			return err
		}

		//result = make([]byte, n, n)
		result = cbuf[:n]

		return nil
	})
	if err != nil {
		return nil, err
	}

	return result, nil
}

func NewIngressServer() *IngressServer {
	client, err := tcp2.NewClient(util.TcpServerPort)
	if err != nil {
		format.Errors("cmd/ingress/app/client.go", 285, err.Error())
		os.Exit(1)
	}

	m := new(map[resource.ResourceString][]resource.Resource)

	listener, err := net.Listen("tcp", ":80")
	if err != nil {
		format.Errors("cmd/ingress/app/client.go", 286, err.Error())
		os.Exit(1)
	}

	c, err := rest.InClusterConfig()
	if err != nil {
		c, err = clientcmd.BuildConfigFromFlags("", defaultPath)
		if err != nil {
			panic(err)
		}
	}
	cset, err := kubernetes.NewForConfig(c)
	if err != nil {
		format.Errors("cmd/ingress/app/client.go", 339, err.Error())
		os.Exit(1)
	}

	informer := informers.NewSharedInformerFactory(cset, 0)
	podLister := informer.Core().V1().Pods().Lister()

	unix := unixsock.NewDefaultUnix(podLister)

	mc := &IngressServer{
		tcpClient: client,
		resource:  m,
		listener:  listener,
		service:   service.NewHystrix(),
		cniUtil:   NewCniUtil(),
		cniSocket: unix,
		informer:  informer,
	}

	client.AddFn("update", mc.updateResource)
	return mc
}
